package com.sbc.user.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.sbc.auth.toolkit.JWTUtil;
import com.sbc.cache.DistributedCache;
import com.sbc.common.toolkit.BeanUtil;
import com.sbc.convention.constants.Constants;
import com.sbc.convention.constants.LoginUsernameTypeEnum;
import com.sbc.convention.exception.ClientException;
import com.sbc.convention.exception.ServiceException;
import com.sbc.convention.pojo.dto.UserInfoDTO;
import com.sbc.design.chain.AbstractChainContext;
import com.sbc.user.dao.entity.TUser;
import com.sbc.user.dao.entity.TUserCancel;
import com.sbc.user.dao.entity.TUserMail;
import com.sbc.user.dao.entity.TUserPhone;
import com.sbc.user.dao.mapper.TUserCancelMapper;
import com.sbc.user.dao.mapper.TUserMailMapper;
import com.sbc.user.dao.mapper.TUserMapper;
import com.sbc.user.dao.mapper.TUserPhoneMapper;
import com.sbc.user.enums.UserChainMarkEnum;
import com.sbc.user.pojo.vo.UserLoginReqVo;
import com.sbc.user.pojo.vo.UserLoginRespVo;
import com.sbc.user.pojo.vo.UserRegisterReqVo;
import com.sbc.user.pojo.vo.UserRegisterRespVo;
import com.sbc.user.service.UserLoginService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.sbc.user.enums.UserRegisterErrorCodeEnum.*;

/**
 * @description: 用户登录接口实现
 * @author: songbaicheng
 * @create: 2024-02-28 10:19
 **/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserLoginServiceImpl implements UserLoginService {

    private final TUserMapper userMapper;
    private final TUserMailMapper userMailMapper;
    private final TUserPhoneMapper userPhoneMapper;
    private final TUserCancelMapper userCancelMapper;
    private final DistributedCache distributedCache;
    private final AbstractChainContext<UserRegisterReqVo> abstractChainContext;
    private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;

    @Override
    public UserLoginRespVo login(UserLoginReqVo userLoginReqVo) {

        // 获取真实用户名
        String usernameOrMailOrPhone = userLoginReqVo.getUsernameOrMailOrPhone();
        String username = switch (identifyInputType(userLoginReqVo.getUsernameOrMailOrPhone())) {
            case "username" -> usernameOrMailOrPhone;
            case "email" -> {
                LambdaQueryWrapper<TUserMail> mailQueryWrapper = Wrappers.lambdaQuery(TUserMail.class)
                        .eq(TUserMail::getMail, usernameOrMailOrPhone);
                yield Optional.ofNullable(userMailMapper.selectOne(mailQueryWrapper))
                        .map(TUserMail::getUsername)
                        .orElseThrow(() -> new ClientException("邮箱不存在！"));
            }
            case "phone" -> {
                LambdaQueryWrapper<TUserPhone> phoneQueryWrapper = Wrappers.lambdaQuery(TUserPhone.class)
                        .eq(TUserPhone::getTelephone, usernameOrMailOrPhone);
                yield Optional.ofNullable(userPhoneMapper.selectOne(phoneQueryWrapper))
                        .map(TUserPhone::getUsername)
                        .orElseThrow(() -> new ClientException("电话不存在！"));
            }
            default -> throw new ClientException("不支持的用户名登录方式！");
        };

        // 根据用户名和密码获取用户信息
        LambdaQueryWrapper<TUser> userQueryWrapper = Wrappers.lambdaQuery(TUser.class)
                .eq(TUser::getUsername, username)
                .eq(TUser::getPassword, userLoginReqVo.getPassword())
                .select(TUser::getId, TUser::getUsername, TUser::getRealName);
        TUser user = userMapper.selectOne(userQueryWrapper);

        // 生成 Token
        if (!ObjectUtil.isEmpty(user)) {
            UserInfoDTO userInfo = UserInfoDTO.builder()
                    .userId(String.valueOf(user.getId()))
                    .username(user.getUsername())
                    .realName(user.getRealName())
                    .build();
            String accessToken = JWTUtil.generateAccessToken(userInfo);

            UserLoginRespVo userLoginRespVo = UserLoginRespVo.builder()
                    .userId(String.valueOf(user.getId()))
                    .username(user.getUsername())
                    .realName(user.getRealName())
                    .accessToken(accessToken)
                    .build();

            // 增加缓存
            distributedCache.put(accessToken, JSON.toJSONString(userLoginRespVo), 720, TimeUnit.MINUTES);

            return userLoginRespVo;
        }

        throw new ClientException("账号不存在或密码错误!");
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public UserRegisterRespVo register(UserRegisterReqVo requestParam) {
        // 登录责任链
        abstractChainContext.handler(UserChainMarkEnum.USER_REGISTER_FILTER.name(), requestParam);
        String username = requestParam.getUsername();

        // 如果没有注册信息再进行注册
        if (!isRegistered(requestParam)) {

            // 插入用户总表
            int inserted = userMapper.insert(BeanUtil.convert(requestParam, TUser.class));
            if (!SqlHelper.retBool(inserted)) {
                log.error("[{}] 插入用户表失败", requestParam);
                throw new ServiceException(USER_REGISTER_FAIL);
            }

            // 插入用户邮箱表
            inserted = userMailMapper.insert(TUserMail.builder()
                    .username(username)
                    .mail(requestParam.getMail())
                    .build());
            if (!SqlHelper.retBool(inserted)) {
                log.error("[{}] 插入用户邮件表失败", requestParam);
                throw new ServiceException(USER_REGISTER_FAIL);
            }

            // 插入用户电话表
            inserted = userPhoneMapper.insert(TUserPhone.builder()
                    .username(username)
                    .telephone(requestParam.getTelephone())
                    .build());
            if (!SqlHelper.retBool(inserted)) {
                log.error("[{}] 插入用户电话表失败", requestParam);
                throw new ServiceException(USER_REGISTER_FAIL);
            }
        }

        // 账户注销表清理账号
        userCancelMapper.delete(Wrappers.update(TUserCancel.builder().username(username).build()));

        return BeanUtil.convert(requestParam, UserRegisterRespVo.class);
    }

    /**
     * 注册信息是否存在
     *
     * @param userRegisterReqVo 用户注册信息
     * @return 是否已经注册
     */
    private boolean isRegistered(UserRegisterReqVo userRegisterReqVo) {

        // 验证证件号是否重复
        LambdaQueryWrapper<TUser> userLambdaQueryWrapper = Wrappers.lambdaQuery(TUser.class)
                .eq(TUser::getUsername, userRegisterReqVo.getUsername())
                .eq(TUser::getDelFlag, 0);
        TUser registeredUser = userMapper.selectOne(userLambdaQueryWrapper);

        if (Optional.ofNullable(registeredUser).isPresent()) {
            log.error("用户名 [{}] 重复注册", userRegisterReqVo.getIdCard());
            throw new ServiceException(USERNAME_REGISTERED);
        }

        // 验证证件号是否重复
        userLambdaQueryWrapper = Wrappers.lambdaQuery(TUser.class)
                .eq(TUser::getIdCard, userRegisterReqVo.getIdCard())
                .eq(TUser::getDelFlag, 0);
        registeredUser = userMapper.selectOne(userLambdaQueryWrapper);

        if (Optional.ofNullable(registeredUser).isPresent()) {
            log.error("证件号码 [{}] 重复注册", userRegisterReqVo.getIdCard());
            throw new ServiceException(ID_CARD_REGISTERED);
        }

        // 验证电话号码是否重复
        LambdaQueryWrapper<TUserPhone> phoneLambdaQueryWrapper = Wrappers.lambdaQuery(TUserPhone.class)
                .eq(TUserPhone::getTelephone, userRegisterReqVo.getTelephone())
                .eq(TUserPhone::getDelFlag, 0);
        TUserPhone registeredPhone = userPhoneMapper.selectOne(phoneLambdaQueryWrapper);

        if (Optional.ofNullable(registeredPhone).isPresent()) {
            log.error("手机号码 [{}] 重复注册", userRegisterReqVo.getTelephone());
            throw new ServiceException(TELEPHONE_REGISTERED);
        }

        // 验证邮件是否重复
        LambdaQueryWrapper<TUserMail> mailLambdaQueryWrapper = Wrappers.lambdaQuery(TUserMail.class)
                .eq(TUserMail::getMail, userRegisterReqVo.getMail())
                .eq(TUserMail::getDelFlag, 0);
        TUserMail registeredMail = userMailMapper.selectOne(mailLambdaQueryWrapper);

        if (Optional.ofNullable(registeredMail).isPresent()) {
            log.error("邮箱 [{}] 重复注册", userRegisterReqVo.getTelephone());
            throw new ServiceException(MAIL_REGISTERED);
        }

        return false;
    }

    /**
     * 判断用户名类型
     *
     * @param usernameOrMailOrPhone 未判断的用户名
     * @return 用户名类型
     */
    private String identifyInputType(String usernameOrMailOrPhone) {

        // 判断邮箱
        Pattern emailPattern = Pattern.compile(Constants.EMAIL_PATTERN);
        Matcher emailMatcher = emailPattern.matcher(usernameOrMailOrPhone);
        if (emailMatcher.matches()) {
            return LoginUsernameTypeEnum.EMAIL.getUsernameType();
        }

        // 判断手机号（这里以中国大陆手机号为例）
        Pattern phonePattern = Pattern.compile(Constants.PHONE_PATTERN);
        Matcher phoneMatcher = phonePattern.matcher(usernameOrMailOrPhone);
        if (phoneMatcher.matches()) {
            return LoginUsernameTypeEnum.PHONE.getUsernameType();
        }

        // 若既不是邮箱也不是手机号，则默认为用户名
        return LoginUsernameTypeEnum.USERNAME.getUsernameType();
    }

    @Override
    public void logout(String accessToken) {
        if (StrUtil.isNotBlank(accessToken)) {
            distributedCache.delete(accessToken);
        }
    }

    @Override
    public Boolean hasUsername(String username) {

        if (!userRegisterCachePenetrationBloomFilter.contains(username)) {
            userRegisterCachePenetrationBloomFilter.add(username);
            return false;
        }
        return true;
    }
}
